-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add "expand" command to Crystal tools #3732
Add "expand" command to Crystal tools #3732
Conversation
Nice. I would like to see some specs. At least some basic one that will ensure some scenarios wont fail: nested classes with modules, top level expansions. I bet you wish there was some kind of index per file/line so you don't need to lookup the whole hierarchy of types. For now we are good, but in the future we might need to enhance de compiler with some of those indices i think. |
Regarding the specs one that will result in multiple expansions should be interesting. And json support with an array of the different levels of expansions. So we can zoom in/out. WDYT? |
When macros are nested, final expansion result may be too complex. In this case it is useful.
If there is a macro in a method definition and the method definition has multiple type signatures, the macro has multiple expansion. For example: macro foo
puts "hello"
end
def bar(x)
foo
end
bar 10
bar "str"
I'll try to write specs. But, it is midnight in Japan, I am going to bed. Good night! |
tiny command! -buffer -nargs=0 CrystalExpand echo s:crystal_expand(expand('%'), getpos('.'))
function! s:crystal_expand(file, pos)
let l:cmd = printf('./bin/crystal tool expand --no-color -c %s:%d:%d %s', a:file, a:pos[1], a:pos[2], a:file)
return system(l:cmd)
endfunction |
Could we run the output through crystal format? Some of the generated macro code isn't particularly beautiful. |
@makenowjust Thank you for this! I think this tool can be useful, I just need to think what's the criteria to merge new tools. Regardless of that, I find it really cool that you are hacking the compiler :-) |
5f9bc34
to
3daafb3
Compare
# in which the (start) location might be in one file and the end location in another. | ||
@target_location.between?(loc_start, loc_end) || loc_start.filename != loc_end.filename | ||
@target_location.between?(loc_start, loc_end) || (!@in_defs && loc_start.filename != loc_end.filename) | ||
# When node generated by macro and macro has yielding block, it also makes `loc_start.filename != loc_end.filename` true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! Can a spec be added also for this case? The specs are at ./spec/compiler/crystal/tools/context_spec.cr
and should be easy to add a sample. There are some unicode chars that act as user cursor so you can easily add specs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is just ad-hoc solution. The radical problem is yield
in macro expansion sets its location, but doesn't reset it. Now, I have an idea for it. I'll make a new pull request to solve it and I am going to revert this commit after this pull request is merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is fixed in #3751. I'll revert and rebase.
3daafb3
to
6e0b362
Compare
@bcardiff Added specs just now! |
5abf7a4
to
9dfcf86
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@makenowjust sorry for the delay. I've made some comments. Some are request for changes.
Would you be able to apply them and rebase this PR?
{% end %} | ||
CODE | ||
|
||
original = <<-CODE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many specs of assert_expand_result_simple could be shortened with a variation: assert_expand_single
? That could strip the line ‸
to get the original. I count 11 occurrences that could be changed with this. WDYT?
@@ -393,6 +397,11 @@ class Crystal::Command | |||
end | |||
end | |||
|
|||
if expand |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rather than adding an expand
param to create_compiler
and compile_no_codegen
. I say to promote no_cleanup
(default false) as param in this two methods.
And add no_cleanup
and wants_doc
to cursor_command
.
The create_compiler needs hierarchy/cursor_command to know which command line options to parse. Since the expand is a cursor_command I think removing expand
param make sense.
class ExpandTrace | ||
JSON.mapping({ | ||
original: {type: String}, | ||
expandeds: {type: Array(String)}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think expandeds
is a word. I would suggest expansions
or progressive_expansions
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I think expansions
is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, but we will get expansions.expansions
. It is very wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expanded
is also used to address a plural quantity, so you can just s/expandeds/expanded
:)
def self.ast_to_s(node) | ||
s = String.build { |io| node.to_s(io, emit_doc: true) } | ||
return s unless node.is_a?(MacroIf) || node.is_a?(MacroFor) | ||
indent = node.location.not_nil!.column_number - 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The case for changing the indentation is not stressed in the specs. If the following code that split/join the lines is removed all specs passed. Some indented if & for macros are needed in the specs.
Note: I don't fully follow why this is needed. Could the node.to_s
output a nice indentation directly. This logic could be refactor later to the ToSVisitor
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MacroIf
and MacroFor
have MacroBody
, it is just sub-string of source text. For example:
begin
{% if 1 == 1 %}
hello
{% end %}
end
MacroIf#to_s
result of {% if 1 == 1 %}...{% end %}
is:
{% if 1 == 1 %}
hello
{% end %}
So, re-indentation is needed. I'll add specs for it.
it "expands macro control {% if %}" do | ||
code = <<-CODE | ||
{% if 1 == 1 %} | ||
‸ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the other specs that use ‸
it is used in the middle of the code. Like: {% ‸if 1 == 1 %}
in this case.
It is stripped before analyzing the code.
It could stay this way if you find it more comfortable, but it is a slight inconsistency.
"crystal tool expand" expands macros for given location. https://asciinema.org/a/96590
ac95b39
to
14d2eae
Compare
@bcardiff Updated. |
🎉 🔨 🔧 Thanks again @makenowjust 🔧 🔨 🎉 |
crystal tool expand
expands macro for given location.See asciinemas: